import base64
import json
import logging
import os
from random import sample
from string import ascii_letters, digits
import time
import uuid
from django.conf import settings
from pathlib import Path
from wechatpayv3 import WeChatPay, WeChatPayType
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from ._struct import Result

# 微信支付商户号（直连模式）或服务商商户号（服务商模式，即sp_mchid)
MCHID = settings.WX_PAY_MCHID

# 商户证书私钥
path = Path(__file__).parent.parent.joinpath('1666883501_20240228_cert').joinpath('apiclient_key.pem')
with open(path) as f:
    PRIVATE_KEY = f.read()

# 商户证书序列号
CERT_SERIAL_NO = settings.WX_PAY_CERT_SERIAL_NO

# API v3密钥， https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_2.shtml
APIV3_KEY = settings.WX_PAY_APIV3_KEY

# APPID，应用ID或服务商模式下的sp_appid
APPID = settings.WX_PAY_APPID

# 回调地址，也可以在调用接口的时候覆盖
NOTIFY_URL = settings.WX_PAY_NOTIFY_URL

# 微信支付平台证书缓存目录，减少证书下载调用次数，首次使用确保此目录为空目录.
# 初始调试时可不设置，调试通过后再设置，示例值:'./cert'
CERT_DIR = None

# 日志记录器，记录web请求和回调细节

logger = logging.getLogger("pay")

# 接入模式:False=直连商户模式，True=服务商模式
PARTNER_MODE = False

# 代理设置，None或者{"https": "http://10.10.1.10:1080"}，详细格式参见https://requests.readthedocs.io/en/latest/user/advanced/#proxies
PROXY = None

# 请求超时时间配置
TIMEOUT = (10, 30)  # 建立连接最大超时时间是10s，读取响应的最大超时时间是30s


class WeChatPayPlus(object):
    def __init__(self):
        # 初始化
        self.wxpay = WeChatPay(
            wechatpay_type=WeChatPayType.NATIVE,
            mchid=MCHID,
            private_key=PRIVATE_KEY,
            cert_serial_no=CERT_SERIAL_NO,
            apiv3_key=APIV3_KEY,
            appid=APPID,
            notify_url=NOTIFY_URL,
            cert_dir=CERT_DIR,
            logger=logger,
            partner_mode=PARTNER_MODE,
            proxy=PROXY,
            # timeout=TIMEOUT
        )

    def pay_native(self, trade_no, description, amount, open_id, attach):
        """
        native下单
        下单成功后即可获取到'code_url'，将'code_url'转换为二维码，并用微信扫码即可进行支付
        Native支付是指商户系统按微信支付协议生成支付二维码，用户再用微信“扫一扫”完成支付的模式
        trade_no 定单号
        attach 附加数据
        amount 金额
        """
        if trade_no:
            out_trade_no = trade_no
        else:
            out_trade_no = ''.join(sample(ascii_letters + digits, 8))
        player = {
            'openid': open_id
        }
        amount = {
            'total': amount,
            'currency': 'CNY'
        }
        code, message = self.wxpay.pay(
            description=description,
            out_trade_no=out_trade_no,
            amount=amount,
            pay_type=WeChatPayType.NATIVE,
            payer=player,
            attach=attach,
        )
        return Result(code=code in [200, 300], data=message)

    def pay_jsapi(self, trade_no, description, amount, open_id, attach):
        """
        jsapi下单
        下单成功后，将prepay_id和其他必须的参数组合传递给JSSDK的wx.chooseWXPay接口唤起支付
        JSAPI支付是指商户通过调用微信支付提供的JSAPI接口，在支付场景中调起微信支付模块完成收款。

        应用场景有：
        线下场所：调用接口生成二维码，用户扫描二维码后在微信浏览器中打开页面后完成支付
        公众号场景：用户在微信公众账号内进入商家公众号，打开某个主页面，完成支付
        PC网站场景：在网站中展示二维码，用户扫描二维码后在微信浏览器中打开页面后完成支付
        """
        if trade_no:
            out_trade_no = trade_no
        else:
            out_trade_no = ''.join(sample(ascii_letters + digits, 8))
        payer = {
            'openid': open_id
        }
        amount = {
            'total': amount,
            'currency': 'CNY'
        }
        code, message = self.wxpay.pay(
            description=description,
            out_trade_no=out_trade_no,
            amount=amount,
            pay_type=WeChatPayType.JSAPI,
            attach=attach,
            payer=payer
        )
        result = json.loads(message)
        if code in range(200, 300):
            prepay_id = result.get('prepay_id')
            timestamp = str(int(time.time()))
            noncestr = str(uuid.uuid4()).replace('-', '')
            package = 'prepay_id=' + prepay_id
            sign = self.wxpay.sign([APPID, timestamp, noncestr, package])
            signtype = 'RSA'
            return Result(code=True, data={
                'appId': APPID,
                'timeStamp': timestamp,
                'nonceStr': noncestr,
                'package': 'prepay_id=%s' % prepay_id,
                'signType': signtype,
                'paySign': sign
            }, message='')
        else:
            return Result(code=False, message=result.get('code'))

    def pay_h5(self, description, trade_no):
        """
        h5下单
        下单成功后，将获取的的h5_url传递给前端跳转唤起支付
        """
        if trade_no:
            out_trade_no = trade_no
        else:
            out_trade_no = ''.join(sample(ascii_letters + digits, 8))
        amount = 1
        scene_info = {'payer_client_ip': '1.2.3.4', 'h5_info': {'type': 'Wap'}}
        code, message = self.wxpay.pay(
            description=description,
            out_trade_no=out_trade_no,
            amount={'total': amount},
            pay_type=WeChatPayType.H5,
            scene_info=scene_info
        )
        return Result(code=code, message=message)

    def pay_mini_program(self, description, trade_no, amount, open_id, attach):
        """
        小程序下单
        下单成功后，将prepay_id和其他必须的参数组合传递给小程序的wx.requestPayment接口唤起支付
        """
        if trade_no:
            out_trade_no = trade_no
        else:
            out_trade_no = ''.join(sample(ascii_letters + digits, 8))
        payer = {
            'openid': open_id
        }
        amount = {
            'total': amount,
            'currency': 'CNY'
        }
        code, message = self.wxpay.pay(
            description=description,
            out_trade_no=out_trade_no,
            amount=amount,
            pay_type=WeChatPayType.MINIPROG,
            attach=attach,
            payer=payer
        )
        result = json.loads(message)
        if code in range(200, 300):
            prepay_id = result.get('prepay_id')
            timestamp = str(int(time.time()))
            noncestr = str(uuid.uuid4()).replace('-', '')
            package = 'prepay_id=' + prepay_id
            sign = self.wxpay.sign(data=[APPID, timestamp, noncestr, package])
            signtype = 'RSA'
            return Result(code=True, data={
                'appId': APPID,
                'timeStamp': timestamp,
                'nonceStr': noncestr,
                'package': 'prepay_id=' + prepay_id,
                'signType': signtype,
                'paySign': sign
            })
        else:
            return Result(code=False, message=result.get('code'))

    def pay_app(self, description, trade_no):
        """
        app下单
        下单成功后，将prepay_id和其他必须的参数组合传递给IOS或ANDROID SDK接口唤起支付
        """
        if trade_no:
            out_trade_no = trade_no
        else:
            out_trade_no = ''.join(sample(ascii_letters + digits, 8))
        amount = 1
        code, message = self.wxpay.pay(
            description=description,
            out_trade_no=out_trade_no,
            amount={'total': amount},
            pay_type=WeChatPayType.APP
        )
        result = json.loads(message)
        if code in range(200, 300):
            prepay_id = result.get('prepay_id')
            timestamp = str(int(time.time()))
            noncestr = str(uuid.uuid4()).replace('-', '')
            package = 'Sign=WXPay'
            sign = self.wxpay.sign(data=[APPID, timestamp, noncestr, prepay_id])
            return Result(code=True, data={
                'appid': APPID,
                'partnerid': MCHID,
                'prepayid': prepay_id,
                'package': package,
                'nonceStr': noncestr,
                'timestamp': timestamp,
                'sign': sign
            })
        else:
            return Result(code=False, message=result.get('code'))

    def notify(self, request):
        """
        解析通知
        """
        result = self.wxpay.callback(request.headers, request.data)
        if result and result.get('event_type') == 'TRANSACTION.SUCCESS':
            resp = result.get('resource')
            appid = resp.get('appid')
            mchid = resp.get('mchid')
            out_trade_no = resp.get('out_trade_no')
            transaction_id = resp.get('transaction_id')
            trade_type = resp.get('trade_type')
            trade_state = resp.get('trade_state')
            trade_state_desc = resp.get('trade_state_desc')
            bank_type = resp.get('bank_type')
            attach = resp.get('attach')
            success_time = resp.get('success_time')
            payer = resp.get('payer')
            amount = resp.get('amount').get('total')

            return Result(code=True, data={
                resp: resp,
                appid: appid,
                mchid: mchid,
                out_trade_no: out_trade_no,
                transaction_id: transaction_id,
                trade_type: trade_type,
                trade_state: trade_state,
                trade_state_desc: trade_state_desc,
                bank_type: bank_type,
                attach: attach,
                success_time: success_time,
                payer: payer,
                amount: amount
            })
        else:
            return Result(code=False, message='失败')


def decrypt(nonce, ciphertext, associated_data, key):
    key_bytes = str.encode(key)
    nonce_bytes = str.encode(nonce)
    ad_bytes = str.encode(associated_data)
    data = base64.b64decode(ciphertext)

    aes_gcm = AESGCM(key_bytes)
    return aes_gcm.decrypt(nonce_bytes, data, ad_bytes)
